#ifndef ETL_DOT_HPP
# define ETL_DOT_HPP
# pragma once

#include <cstdint>

#include "simd.hpp"

namespace etl
{

namespace dot_impl
{
#if defined(__SSE__)
  template <typename E1, typename E2, ::std::size_t a, ::std::size_t... Is>
  typename ::std::enable_if<bool(sizeof...(Is) <= 3) &&
    ::std::is_same<float,
      prod_type<typename E1::value_type, typename E2::value_type> >{},
    prod_type<typename E1::value_type, typename E2::value_type>
  >::type
  get(E1 const& lhs, E2 const& rhs) noexcept
  {
    using value_type =
      prod_type<typename E1::value_type, typename E2::value_type>;

    auto const v(_mm_mul_ps(
      get_m128f<a, Is...>(lhs), get_m128f<a, Is...>(rhs)));

    value_type r(v[0]);

    ::std::initializer_list<int>{(r += v[Is - a], 0)...};

    return r;
  }

  template <typename E1, typename E2, ::std::size_t a, ::std::size_t b,
    ::std::size_t c, ::std::size_t d, ::std::size_t... Is>
  typename ::std::enable_if<bool(sizeof...(Is)) &&
    ::std::is_same<float,
      prod_type<typename E1::value_type, typename E2::value_type> >{},
    prod_type<typename E1::value_type, typename E2::value_type>
  >::type
  get(E1 const& lhs, E2 const& rhs) noexcept
  {
    prod_type<typename E1::value_type, typename E2::value_type> tmp;

    __m128 const v(_mm_mul_ps(
      get_m128f<a, Is...>(lhs), get_m128f<a, Is...>(rhs)));

    return v[0] + v[1] + v[2] + v[3] + get<E1, E2, Is...>(lhs, rhs);
  }
#endif // __SSE__
#if defined(__SSE2__)
  template <typename E1, typename E2, ::std::size_t a, ::std::size_t... Is>
  typename ::std::enable_if<bool(sizeof...(Is) <= 1) &&
    ::std::is_same<double,
      prod_type<typename E1::value_type, typename E2::value_type> >{},
    prod_type<typename E1::value_type, typename E2::value_type>
  >::type
  get(E1 const& lhs, E2 const& rhs) noexcept
  {
    using value_type =
      prod_type<typename E1::value_type, typename E2::value_type>;

    __m128d const v(_mm_mul_pd(get_m128d<a, Is...>(lhs),
      get_m128d<a, Is...>(rhs)));

    value_type r(v[0]);

    ::std::initializer_list<int>{(r += v[Is - a], 0)...};

    return r;
  }

  template <typename E1, typename E2, ::std::size_t a, ::std::size_t b,
    ::std::size_t... Is>
  typename ::std::enable_if<bool(sizeof...(Is)) &&
    ::std::is_same<double,
      prod_type<typename E1::value_type, typename E2::value_type> >{},
    prod_type<typename E1::value_type, typename E2::value_type>
  >::type
  get(E1 const& lhs, E2 const& rhs) noexcept
  {
    prod_type<typename E1::value_type, typename E2::value_type> tmp;
    auto const v(_mm_mul_pd(get_m128d<a, Is...>(lhs),
      get_m128d<a, Is...>(rhs)));

    return v[0] + v[1] + get<E1, E2, Is...>(lhs, rhs);
  }
#endif // __SSE2__
#if defined(__GNUC__) && !defined(__SSE__) && !defined(__SSE2__)
  template <typename vec_type, ::std::size_t ...Is, typename E>
  inline typename ::std::enable_if<!is_vector<E>{}, vec_type>::type
  get_vec(E const& e) noexcept
  {
    return {e(Is)...};
  }

  template <typename vec_type, ::std::size_t ...Is, typename E>
  inline typename ::std::enable_if<is_vector<E>{}, vec_type const&>::type
  get_vec(E const& e) noexcept
  {
    return *reinterpret_cast<vec_type const*>(e.cdata());
  }

  template <typename E1, typename E2, ::std::size_t a, ::std::size_t... Is>
  prod_type<typename E1::value_type, typename E2::value_type>
  get(E1 const& lhs, E2 const& rhs) noexcept
  {
    using value_type =
      prod_type<typename E1::value_type, typename E2::value_type>;
    typedef value_type vec_type __attribute__ ((vector_size(
      detail::map_extent<E1::dimension>{} * sizeof(value_type))));

    vec_type const v(get_vec<vec_type, a, Is...>(lhs) *
      get_vec<vec_type, a, Is...>(rhs));

    value_type r{v[0]};

    ::std::initializer_list<int>{(r += v[Is], 0)...};

    return r;
  }
#endif
#if !defined(__GNUC__) && !defined(__SSE__) && !defined(__SSE2__)
  template <typename E1, typename E2, ::std::size_t i, ::std::size_t... Is>
  inline constexpr typename ::std::enable_if<
    bool(!sizeof...(Is)), prod_type<typename E1::value_type,
      typename E2::value_type>
  >::type
  get(E1 const& lhs, E2 const& rhs) noexcept
  {
    return lhs(i) * rhs(i);
  }

  template <typename E1, typename E2, ::std::size_t i, ::std::size_t... Is>
  inline constexpr typename ::std::enable_if<
    bool(sizeof...(Is)), prod_type<typename E1::value_type,
      typename E2::value_type>
  >::type
  get(E1 const& lhs, E2 const& rhs) noexcept
  {
    return lhs(i) * rhs(i) + get<Is...>(lhs, rhs);
  }
#endif

  template<typename E1, typename E2, ::std::size_t... Is>
  inline constexpr prod_type<typename E1::value_type,
    typename E2::value_type>
  get(
    vector_expression<typename E1::value_type, E1::dimension, E1> const& lhs,
    vector_expression<typename E2::value_type, E2::dimension, E2> const& rhs,
    detail::indices<Is...> const) noexcept
  {
    return get<E1, E2, Is...>(lhs, rhs);
  }
}

template <typename E1, typename E2>
inline constexpr prod_type<typename E1::value_type, typename E2::value_type>
dot(vector_expression<typename E1::value_type,
  E1::dimension, E1> const& e1,
  vector_expression<typename E2::value_type,
  E2::dimension, E2> const& e2) noexcept
{
  return dot_impl::get(e1, e2, detail::make_indices<E1::dimension>());
}

}

#endif // ETL_DOT_HPP
